// Copyright (c) 2008 screamingtarget@gmail.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.


#pragma once


#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <deque>

#include "..\PortAudio\include\portaudio.h"


using boost::asio::ip::tcp;


const UINT RWM_PORTAUDIOSTATUS = RegisterWindowMessage( _T("RWM_PORTAUDIOSTATUS__23336352_D2E5_4e90_895A_13E73693D131") );
const UINT RWM_CONNECTIONLOST = RegisterWindowMessage( _T("RWM_CONNECTIONLOST__6E1DAC29_7B45_4369_9183_94F586887BC5") );


class Connection : public boost::enable_shared_from_this< Connection >,
   private boost::noncopyable
{
public:

   Connection( boost::asio::io_service& io_service, HWND hwndNotify ) : 
      socket_( io_service ), hwndNotify_( hwndNotify ), stream_( NULL )
   {
   }

   void Start()
   {
      SetupPortAudio();

      socket_.async_read_some( boost::asio::buffer( readBuffer_ ),
         boost::bind( &Connection::HandleRead, shared_from_this(),
         boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred ) );
   }

   void Stop()
   {
      CleanupPortAudio();
      socket_.close();
   }

   void Write( const void* data, size_t size )
   {
      boost::asio::async_write( socket_, boost::asio::buffer( data, size ),
         boost::bind( &Connection::HandleWrite, shared_from_this(),
         boost::asio::placeholders::error ) );
   }

   boost::asio::ip::tcp::socket& socket() { return socket_; }

private:

   boost::asio::ip::tcp::socket socket_;

   HWND hwndNotify_;

   PaStream* stream_;
   PaError error_;
   PaStreamParameters inputParameters_;

   enum { ReadBufferSize = 32 };
   char readBuffer_[ReadBufferSize];

   enum
   {
      SampleRate = 44100,
      NumChannels = 2,
      SampleSize = 2,
      FramesPerBuffer = 320
   };


   void HandleRead( const boost::system::error_code& error,
      std::size_t /*bytesTransferred*/ )
   {
      if ( error )
      {
         ::PostMessage( hwndNotify_, RWM_CONNECTIONLOST, 0, 0 );
         Stop();
      }
      else
      {
         socket_.async_read_some( boost::asio::buffer( readBuffer_ ),
            boost::bind( &Connection::HandleRead, shared_from_this(),
            boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred ) );
      }
   }

   void HandleWrite( const boost::system::error_code& error )
   {
   }

   bool SetupPortAudio()
   {
      error_ = Pa_Initialize();

      if ( paNoError != error_ )
      {
         return false;
      }

      inputParameters_.device = Pa_GetDefaultInputDevice();

      if ( paNoDevice == inputParameters_.device )
      {
         CleanupPortAudio();
         return false;
      }

      inputParameters_.channelCount = NumChannels;
      inputParameters_.sampleFormat = paInt16;
      inputParameters_.suggestedLatency = Pa_GetDeviceInfo( inputParameters_.device )->defaultLowInputLatency;
      inputParameters_.hostApiSpecificStreamInfo = NULL;

      error_ = Pa_OpenStream( &stream_, &inputParameters_, NULL, SampleRate,
         FramesPerBuffer, paNoFlag, PACallback, this );

      if ( paNoError != error_ )
      {
         CleanupPortAudio();
         return false;
      }

      error_ = Pa_StartStream( stream_ );

      if ( paNoError != error_ )
      {
         CleanupPortAudio();
         return false;
      }

      return true;
   }

   void CleanupPortAudio()
   {
      if ( stream_ )
      {
         Pa_AbortStream( stream_ );
         Pa_CloseStream( stream_ );
      }

      Pa_Terminate();

      stream_ = NULL;
   }

   static int PACallback( const void* input, void* output, unsigned long frameCount,
      const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags,
      void* userData )
   {
      Connection* me = reinterpret_cast< Connection* >( userData );

      if ( statusFlags )
      {
         ::PostMessage( me->hwndNotify_, RWM_PORTAUDIOSTATUS, statusFlags, 0 );
      }

      size_t numBytes = frameCount * SampleSize * NumChannels;

      me->Write( input, numBytes );

      return paContinue;
   }
};


typedef boost::shared_ptr< Connection > ConnectionPtr;